/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.services;

import javax.annotation.security.RolesAllowed;
import org.directwebremoting.annotations.RemoteMethod;
import org.internna.iwebjtracker.i18n.MessageResolver;
import org.internna.iwebjtracker.issue.model.Comment;
import org.internna.iwebjtracker.issue.model.Issue;
import org.internna.iwebjtracker.issue.model.Issue.Link;
import org.internna.iwebjtracker.issue.model.Issue.ResolutionStatus;
import org.internna.iwebjtracker.issue.model.Issue.Status;
import org.internna.iwebjtracker.project.model.IWebJTrackerUser;
import org.internna.iwebjtracker.project.model.Module;
import org.internna.iwebjtracker.project.model.Project;
import org.internna.iwebjtracker.project.model.Version;
import org.internna.iwebjtracker.project.validation.Errors;
import org.internna.iwebmvc.RollbackException;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.model.UUID;
import org.springframework.context.MessageSource;

import org.springframework.transaction.annotation.Transactional;
import static org.internna.iwebmvc.utils.StringUtils.hasText;
import static org.internna.iwebjtracker.project.validation.Errors.*;
import static org.internna.iwebjtracker.boot.tasks.CreateRolesStartupTask.ROLE_DEVELOPER;
import static org.internna.iwebjtracker.boot.tasks.CreateRolesStartupTask.ROLE_ISSUE_CREATOR;
import static org.internna.iwebjtracker.services.AbstractProjectManager.ROLE_PROJECT_MANAGER;

/**
 * Base methods to validate and create issues.
 *
 * @author Jose Noheda
 * @since 1.0
 */
public abstract class AbstractIssueManager<M extends Module<P, M>, V extends Version<U, P, V>, U extends IWebJTrackerUser, P extends Project<?, M, V, U, P>, I extends Issue<M, V, U, P, C, I> & DomainEntity, C extends Comment<U, C, I>> extends MessageResolver implements IssueManager<M, V, U, P, I, C> {

    /**
     * Sets the configured message resolution bean.
     *
     * @param messageSource any
     */
    public AbstractIssueManager(MessageSource messageSource) {
        super(messageSource);
    }

    /**
     * Checks if null issue an proceeds with field validation.
     *
     * @param issue any
     * @return any
     */
    @Override public final Errors validateIssue(I issue) {
        return issue == null ? NULL_ISSUE : validateRequiredIssueFields(issue);
    }

    /**
     * Checks required fields (type, status, resolution status, severity, creator, summary, project, module, affected version).
     *
     * @param issue a non null issue
     * @return any
     */
    protected final Errors validateRequiredIssueFields(I issue) {
        Errors error = null;
        if (issue.getType() == null) {
            error = NULL_ISSUE_TYPE;
        } else if (issue.getSeverity() == null) {
            error = NULL_SEVERITY;
        } else if (issue.getParentProject() == null) {
            error = NULL_PROJECT_VALIDATION;
        } else if (issue.getParentModule() == null) {
            error = NULL_MODULE;
        } else if (issue.getAffectedVersion() == null) {
            error = NULL_VERSION;
        } else if (!hasText(issue.getSummary())) {
            error = ISSUE_HAS_NO_SUMMARY;
        }
        return error;
    }

    /**
     * Validates an issue and proceeds to save it into the persistent storage.
     *
     * @param issue any
     * @return a primary key
     * @throws RollbackException so DWR can handle the validation error if any
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_ISSUE_CREATOR)
    @Override public final synchronized UUID createIssue(I issue) {
        Errors error = validateIssue(issue);
        if (error == null) {
            persistIssue(issue);
            return issue.getId();
        }
        throw new RollbackException(resolve(error));
    }

    /**
     * Makes all the validation and proceeds only with valid issues.
     *
     * @param origin any
     * @param target any
     * @param type any
     * @return a not empty string for invalid requests, null otherwise
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_DEVELOPER)
    @Override public final String linkWith(I origin, I target, Link type) {
        if ((origin != null) && (target != null) && (type != null)) {
            I linker = loadIssue(origin);
            I linked = loadIssue(target);
            return resolve(linker != null && linked != null ? proceedWithLinking(linker, linked, type) : NULL_ISSUE);
        }
        return resolve(MISSING_INFO);
    }

    private Errors proceedWithLinking(I origin, I target, Link type) {
        if (!origin.equals(target)) {
            return Issue.Status.CLOSED.equals(origin.getStatus()) ? ISSUE_CLOSED : linkWithUser(origin, target, type);
        }
        return ISSUE_EQUALS;
    }

    /**
     * Checks the issue provided and continues flow if validation was successful.
     *
     * @param issue any
     * @param status any
     * @param comment any
     * @return the i18n translated error or null
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_DEVELOPER)
    @Override public final String closeIssue(I issue, ResolutionStatus status, String comment) {
        if (issue != null) {
            I loaded = loadIssue(issue);
            if (loaded != null) {
                return resolve(Issue.Status.CLOSED.equals(loaded.getStatus()) ? ISSUE_CLOSED : proceedWithClose(loaded, status, comment));
            }
        }
        return resolve(NULL_ISSUE);
    }

    /**
     * Sets the targte version for the issue as long as the values are valid and the version is not closed.
     *
     * @param issue any
     * @param version any
     * @return any
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_PROJECT_MANAGER)
    @Override public String applyTargetVersion(I issue, V version) {
        if (issue != null) {
            I loaded = loadIssue(issue);
            if (loaded != null) {
                V loadedVersion = loadVersion(version);
                if (loadedVersion != null) {
                    return resolve(applyVersion(loaded, loadedVersion));
                }
                return resolve(NULL_VERSION);
            }
        }
        return resolve(NULL_ISSUE);
    }

    private Errors applyVersion(I issue, V version) {
        if (!version.isClosed()) {
            if (!Status.CLOSED.equals(issue.getStatus())) {
                issue.setTargetVersion(version);
                persistIssue(issue);
                return null;
            }
            return ISSUE_CLOSED;
        }
        return VERSION_CLOSED;
    }

    /**
     * Delegates the closing logic.
     *
     * @param issue a non null issue with a valid status (not closed)
     * @param status any
     * @param comment any
     */
    protected abstract Errors proceedWithClose(I issue, ResolutionStatus status, String comment);

    /**
     * Creates or updates an issue.
     *
     * @param issue any
     */
    protected abstract void persistIssue(I issue);

    /**
     * Returns the valid matching issue in the managed context.
     *
     * @param issue any
     * @return any
     */
    protected abstract I loadIssue(I issue);

    protected abstract V loadVersion (V version);
    /**
     * Delegates logic to subclass which is able to retrieve the logged in user.
     *
     * @param origin a not null valid issue
     * @param target a not null valid issue
     * @param type
     * @return
     */
    protected abstract Errors linkWithUser(I origin, I target, Link type);

}
